﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Globalization;

using System.Numerics;

using NumDouble = MathNet.Numerics.LinearAlgebra.Double;
using NumComplex = MathNet.Numerics.LinearAlgebra.Complex;

namespace Estimador_EDAL.Estimador
{
    public class CRedeElet
    {
        private List<uint> m_ListBarr;
        private List<CCircuito> m_ListCirc;
        private NumComplex.DenseMatrix m_Ybus;

        #region Construção do Objeto

        public CRedeElet()
        {
            Defaults();
        }

        public CRedeElet(CRedeElet rede)
        {
            Copy(rede);
        }

        public CRedeElet(List<CCircuito> listCirc)
        {
            m_ListCirc = listCirc;
            Atualiza_Barr();
            Atualiza_Ybus();
        }

        private void Defaults()
        {
            m_ListBarr = new List<uint>();
            m_ListCirc = new List<CCircuito>();
            m_Ybus = null;
        }

        private void Copy(CRedeElet rede)
        {
            m_ListBarr.Clear();
            m_ListBarr = rede.m_ListBarr.GetRange(0, rede.m_ListBarr.Count);

            m_ListCirc.Clear();
            foreach (CCircuito circ in rede.m_ListCirc)
                m_ListCirc.Add(new CCircuito(circ));

            m_Ybus = rede.m_Ybus;
        }

        public string ValidaDados(bool isReportZerado)
        {
            //falta implementar!!!!!!!!!!!!!!!!!!!!!!!
            return "";
        }

        #endregion Construção do Objeto

        #region Get's e Set's

        public ReadOnlyCollection<uint> ListBarr
        {
            get
            {
                return m_ListBarr.AsReadOnly();
            }
        }

        public ReadOnlyCollection<CCircuito> ListCirc
        {
            get
            {
                return m_ListCirc.AsReadOnly();
            }
        }

        public NumComplex.DenseMatrix Ybus
        {
            get { return m_Ybus; }
        }

        #endregion Get's e Set's

        #region Rotinas Privadas - Atualização de Barras e Ybus

        private void Atualiza_Barr()
        {
            m_ListBarr.Clear();
            foreach (CCircuito circ in m_ListCirc)
            {
                if ((circ.BarraDE != 0) && (!m_ListBarr.Contains(circ.BarraDE)))
                    m_ListBarr.Add(circ.BarraDE);
                if ((circ.BarraPARA != 0) && (!m_ListBarr.Contains(circ.BarraPARA)))
                    m_ListBarr.Add(circ.BarraPARA);
            }
        }

        //Se (De == Para): não importa se é 1-1 ou 0-0, não entra na Ybus.
        //Se (De != Para), podem ser 3 situações:
        //                                       n-0 ---> shunt da barra n
        //                                       0-n ---> shunt da barra n
        //                                       n-m ---> série entre n e m (LT ou TR)
        private void Atualiza_Ybus()
        {
            if (m_ListBarr.Count == 0)
            {
                m_Ybus = null;
                return;
            }

            m_Ybus = new NumComplex.DenseMatrix(m_ListBarr.Count, m_ListBarr.Count, Complex.Zero);

            for (int i = 0; i < m_ListCirc.Count; i++)
            {
                CCircuito circ = m_ListCirc[i];

                int pos_de = (circ.BarraDE == 0) ? int.MaxValue : m_ListBarr.FindIndex(delegate(uint obj) { return obj == circ.BarraDE; });
                int pos_para = (circ.BarraPARA == 0) ? int.MaxValue : m_ListBarr.FindIndex(delegate(uint obj) { return obj == circ.BarraPARA; });

                //0 - testa falha em m_ListBarr
                if ((pos_de == -1) || (pos_para == -1))
                {
                    System.Diagnostics.Debug.WriteLine("Falha (CTopologia.Atualiza_Ybus): encontrada uma barra não listada em m_ListBarr.");
                    return;
                }
                
                //1 - ignora ramo com (de == para).
                if (pos_de == pos_para)
                    continue;

                //2 - trata ramo shunt (0-para). Ignora campos Yshij e TAPij...
                if (pos_de == int.MaxValue)
                {
                    m_Ybus[pos_para, pos_para] += Complex.Reciprocal(circ.Zij);
                    continue;
                }

                //3 - trata ramo shunt (de-0). Ignora campos Yshij e TAPij...
                if (pos_para == int.MaxValue)
                {
                    m_Ybus[pos_de, pos_de] += Complex.Reciprocal(circ.Zij);
                    continue;
                }

                //4 - trata ramo série. 
                //É um TR? Ignora Yshij...
                if (Complex.Abs(circ.TAPij) != 1.0)
                {
                    m_Ybus[pos_de, pos_de] += Complex.Reciprocal(circ.Zij) / (Complex.Abs(circ.TAPij) * Complex.Abs(circ.TAPij));
                    m_Ybus[pos_para, pos_para] += Complex.Reciprocal(circ.Zij);
                    m_Ybus[pos_de, pos_para] -= Complex.Reciprocal(circ.Zij) / Complex.Conjugate(circ.TAPij);
                    m_Ybus[pos_para, pos_de] -= Complex.Reciprocal(circ.Zij) / circ.TAPij;
                }
                //É uma LT? Ignora TAPij...
                else
                {
                    m_Ybus[pos_de, pos_de] += Complex.Reciprocal(circ.Zij) + 0.5 * circ.Yshij;
                    m_Ybus[pos_para, pos_para] += Complex.Reciprocal(circ.Zij) + 0.5 * circ.Yshij;
                    m_Ybus[pos_de, pos_para] -= Complex.Reciprocal(circ.Zij);
                    m_Ybus[pos_para, pos_de] -= Complex.Reciprocal(circ.Zij);
                }
            }
        }

        #endregion

        #region Rotinas Públicas - Add e Remove CCircuito

        public bool AddCircuito(CCircuito circ)
        {
            bool achou = m_ListCirc.Contains(circ);
            if (achou)
                return false;

            m_ListCirc.Add(circ);
            Atualiza_Barr();
            Atualiza_Ybus();
            return true;
        }

        public bool RemoveCircuito(CCircuito circ)
        {
            bool achou = m_ListCirc.Contains(circ);
            if (!achou) 
                return false;

            m_ListCirc.Remove(circ);
            Atualiza_Barr();
            Atualiza_Ybus();
            return true;
        }

        #endregion

        #region Rotinas Públicas - DE e PARA formato texto

        public void CarregaDeTxt(string str)
        {
            string[] str_Partes = str.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string str_Circ in str_Partes)
            {
                string str_Circ_Tmp = str_Circ.Trim();
                if (str_Circ_Tmp.StartsWith("("))
                    continue;

                CCircuito circ = new CCircuito();
                circ.CarregaDeTxt(str_Circ);
                m_ListCirc.Add(circ);
            }

            Atualiza_Barr();
            Atualiza_Ybus();
        }

        public string DescarregaParaTxt(bool isReportZerado)
        {
            string ret = (isReportZerado) ? "" : "\n\n";

            ret += "Lista de Barras:\n\n";
            int i = -1;
            foreach (uint ibarr in m_ListBarr)
                ret += "Barra " + (++i).ToString("000") + " --> " + ibarr.ToString("000") + "\n";

            ret += "\nLista de Circuitos:\n\n";
            foreach (CCircuito circ in m_ListCirc)
                ret += circ.DescarregaParaTxt() + "\n";

            ret += "\nYbus:\n\n";
            CultureInfo formatProvider = (CultureInfo)CultureInfo.InvariantCulture.Clone();
            formatProvider.TextInfo.ListSeparator = " ";
            ret += m_Ybus.ToString("#0.000000\t", formatProvider);

            return ret;
        }

        #endregion
    }
}
